﻿using System;
using System.Activities;
using System.Activities.XamlIntegration;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Web.Routing;
using System.Xaml;

namespace MvcFlow.InstanceProviders
{
    public class DynamicWorkflowInstanceProvider : IWorkflowInstanceProvider
    {
        private readonly IWorkflowHost                          workflowHost;
        private readonly IWorkflowDefinitionProvider            workflowDefinitionProvider;
        private readonly ConcurrentDictionary<string, Activity> activityCache = new ConcurrentDictionary<string, Activity>(StringComparer.OrdinalIgnoreCase);
        private readonly Assembly                               localAssembly;

        public async Task<Activity> GetWorkflowInstance(RequestContext requestContext, string workflowName)
        {
            var shouldBeCached = !workflowHost.IsDebugging(requestContext);

            if (shouldBeCached)
            {
                Activity result = null;

                if (!activityCache.TryGetValue(workflowName, out result))
                {
                    var activity = await GetWorkflowInstanceCore(requestContext, workflowName);

                    if (activity == null)
                    {
                        return null;
                    }

                    if (activityCache.TryAdd(workflowName, activity))
                    {
                        return activity;
                    }

                    activityCache.TryGetValue(workflowName, out result);
                    return result;
                }

                return result;
            }

            return await GetWorkflowInstanceCore(requestContext, workflowName);
        }

        private async Task<Activity> GetWorkflowInstanceCore(RequestContext requestContext, string workflowName)
        {
            var workflowMarkup = await workflowDefinitionProvider.GetWorkflowDefinition(requestContext, workflowName);

            if (!string.IsNullOrWhiteSpace(workflowMarkup))
            {
                using (var markupReader = new StringReader(workflowMarkup))
                {
                    var activityReader = ActivityXamlServices.CreateReader(new XamlXmlReader(markupReader, new XamlXmlReaderSettings() { LocalAssembly = localAssembly }));
                    return ActivityXamlServices.Load(activityReader,new ActivityXamlServicesSettings() { CompileExpressions = true});
                }
            }

            return null;
        }

        public DynamicWorkflowInstanceProvider(IWorkflowHost workflowHost, IWorkflowDefinitionProvider workflowDefinitionProvider,Assembly localAssembly = null )
        {
            this.localAssembly              = localAssembly ?? new StackTrace().GetFrame(1).GetMethod().DeclaringType.Assembly;
            this.workflowHost               = workflowHost;
            this.workflowDefinitionProvider = workflowDefinitionProvider;
            this.workflowDefinitionProvider.Changed += provider => activityCache.Clear();
        }
    }
}